home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1997 September
/
Macworld (1997-09).dmg
/
Shareware World
/
Utilities
/
Text Processing
/
Alpha
/
Tcl
/
Modes
/
bibtex.tcl
< prev
next >
Wrap
Text File
|
1996-09-25
|
44KB
|
1,406 lines
###########################################################################
# bibtex.tcl
#
# This file contains a package of Tcl routines that add support for using
# and maintaining BibTeX citation databases to Alpha.
#
# See the accompanying file, "BibTeX Help", for a complete description.
# (Unfortunately, it's a bit out of date right now - stay tuned (WTP 6/95))
#
###########################################################################
# Notes:
#
# By default, only the required fields are included when a new bib entry
# is created. You can select any other set of fields to be used by adding
# an appropriate entry to the 'myFld' array, following the example for the
# Article entry, further below. You shouldn't change the 'rqdFld' or
# 'optFld' arrays, since these will (some day) be used for syntax checking.
#
###########################################################################
# written by Tom Pollard (pollard@cucbs.chem.columbia.edu)
#
# Version History
#
# 2.7 (7/95) 'stdAbbrevs' modeVar added for setting predefined abbrevs
# month names included as predefined abbrevs
# 'alignEquals' formatting flag added.
# 2.62 (7/95) field delimiters suppressed if field data is an abbreviation
# unindexed .bib files are indexed automatically upon opening
# 2.61 (7/95) fixed "SearchFields" bug.
# 2.6 (6/95) 'zapEmptyFields' flag forces optional fields to be removed
# when reformatting an entry.
# 'markStrings' flag controls whether @string entries are included in
# the marks menu.
# 'descendingYears' flag controls whether sorts are in ascending or
# descending chronological order.
# Sorts all use the year as either primary or secondary sort key now.
# 'copyCiteKey' command copies the citekey of the current entry to the
# clipboard.
# Cmd-double-clicking implemented to resolve abbreviations and crossrefs.
# Fixed bug in faster getFields proc (comma-after-last-field problems)
# Fixed minor bugs in author sorting.
# 2.5 (6/95) Fixed bug in formatEntry, whereby '#' concatenations were lost
# formatEntry completely ignores @string entries now
# Entry-parsing code (getFields, getFldVal) cleaned up,
# should also be a little bit faster now.
# formatAllEntries now starts working from the current entry
# 2.41 (6/95) Updates for compatibility with revised LaTeX mode
# Automatic conversion of international characters dropped
# (irreconcilable problems with non-US keyboards).
# 2.4 (5/95) Fixed bugs in parsing of EndNote-created bib files
# 2.3 (4/95) International characters converted to TeX codes (optionally).
# 'findEntries' bug fixed (no longer returns multiple hits)
# 2.2 (12/94) 'formatEntries' won't quote fields that contain "#".
# 'segregateStrings' flag forces string defs to sort to the top.
# 2.11(12/94) Bug fixes in 'formatAllEntries'.
# 2.1 (12/94) 'countEntries' command added.
# 'formatAllEntries' command added; it's a bit clunky, but more robust
# than any quicker alternative I considered.
# Cross-referenced entries now sort to the bottom in all sorts.
# 'crossref' field now included.
# 2.0 (9/94) 'formatEntry' and 'newEntry' line up fields better.
# 'nextEntry' and 'prevEntry' skip @string defs
# 'formatEntry' automtically goes to next entry afterwards.
# 'sortByCitekey' ignores case of cite keys.
# 'fillColumn' included as default modeVar.
# 'getEntry' alerts user to badly delimited entries.
# 1.9 (9/94) 'getFields' should now correctly parse any legal entry.
# 'language' field now included.
# Default values for new fields (eg 'language') may be defined
# 'preferBraces' replaced by 'fieldBraces' and 'entryBraces'.
# line-wrapping is done on reformatted entries.
# '@string' entries preserved in sorts.
# text before first entry and after last entry are preserved
# by sorts.
# 1.8 (8/94) "getEntry" now recognizes parens as entry delimiters
# 1.7 (8/94) Bug fixes and accomodations to latex.tcl v2.2
# Template insertion streamlined
# Choose multiple fields at a time from list dialog
# 1.6 (8/94) "preferBraces" allows braces or quotes to be default for
# new or reformatted entries,
# Menu built using $entryNames and $fieldNames,
# 'sortByAuthors' can now sort using last author first,
# and is a bit faster,
# 'formatEntry' rewrites entries in canonical format,
# More customization of canonical format allowed ('indentString')
# Bib mode definition adapted to Alpha 5.90.
# 1.5 (7/94) "sortByAuthors" is now robust (I think),
# Mode of new windows now set correctly.
# 1.4 (7/94) Added sorting by authors, but still only semi-functional,
# Added regexp searching by field,
# "getEntry" bugs fixed.
# 1.2 (7/94) Bib mode definition adapted to Alpha 5.85,
# Added bib-file marking (bibMarkFile),
# Entry and field creation now controlled by data arrays.
# 1.1 (6/94) Custom BibTeX icon,
# Added simple search capability (matchingEntries).
# 1.0 (9/93) First stable version.
#
###########################################################################
# This package was inspired by the LaTeX package (latex.tcl), written by
#
# Richard T. Austin <austin@eecs.umich.edu> , and (currently),
# Tom Scavo <trscavo@syr.edu>
#
###########################################################################
############################################################################
# Cause latex.tcl to be loaded by calling a dummy procedure defined in that
# file. This is necessary to get the TeX menu, and to load the 8-bit ASCII
# to TeX conversion routines.
#
dummyTeX
###########################################################################
# BibTeX Key Bindings.
###########################################################################
# abbreviations: <o> = option, <z> = control, <s> = shift, <c> = command
#
bind 'b' <sz> selectEntry "Bib"
bind 'n' <sz> nextEntry "Bib"
bind 'p' <sz> prevEntry "Bib"
bind 'f' <sz> searchFields "Bib"
bind 'm' <sz> searchEntries "Bib"
bind 'l' <sz> formatEntry "Bib"
# tab stops:
bind '\t' nextTabStop "Bib"
bind '\t' <s> prevTabStop "Bib"
bind '\t' <z> {nthTabStop 0} "Bib"
bind '\t' <c> deleteTabStops "Bib"
###########################################################################
# Data Definitions
###########################################################################
###########################################################################
# Define the data arrays that contain the names of the required,
# optional, and preferred fields for each entry type.
#
# The index names of the rqdFld() array _define_ the valid entry types
# recognized by the program.
#
set rqdFld(article) {author title journal year}
set optFld(article) {volume number pages month note}
set myFld(article) {author title journal volume pages year note}
set rqdFld(book) {author title publisher year}
set optFld(book) {editor volume number series address edition month note}
set rqdFld(booklet) {title}
set optFld(booklet) {author howpublished address month year note}
set rqdFld(conference) {author title booktitle year}
set optFld(conference) {editor volume number series pages organization publisher address month note}
set rqdFld(inBook) {author title chapter publisher year}
set optFld(inBook) {editor pages volume number series address edition month type note}
set rqdFld(inCollection) {author title booktitle publisher year}
set optFld(inCollection) {editor volume number series type chapter pages address edition month note}
set rqdFld(inProceedings) {author title booktitle year}
set optFld(inProceedings) {editor volume number series pages organization publisher address month note}
set rqdFld(manual) {title}
set optFld(manual) {author organization address edition year month note}
set rqdFld(mastersThesis) {author title school year}
set optFld(mastersThesis) {address month note type}
set rqdFld(misc) {}
set optFld(misc) {author title howpublished year month note}
set rqdFld(phdThesis) {author title school year}
set optFld(phdThesis) {address month type note}
set rqdFld(proceedings) {title year}
set optFld(proceedings) {editor volume number series publisher organization address month note}
set rqdFld(techReport) {author title institution year}
set optFld(techReport) {type number address month note}
set rqdFld(unpublished) {author title note}
set optFld(unpublished) {year month}
set entryNames [lsort [array names rqdFld]]
set customEntries [lsort [array names myFld]]
###########################################################################
# Define an array of flags indicating whether the data a given field
# type should be quoted. The actual characters used to quote the field are
# given by $bibOpenQuote and $bibCloseQuote, which are set by the routine
# 'bibFieldDelims' according to the flag $fieldBraces.
#
# Note that the index names of the useBrace() array _define_ the valid
# field types recognized by the program.
#
set useBrace(address) 1
set useBrace(annote) 1
set useBrace(author) 1
set useBrace(booktitle) 1
set useBrace(chapter) 0
set useBrace(crossref) 1
set useBrace(edition) 1
set useBrace(editor) 1
set useBrace(howpublished) 1
set useBrace(institution) 1
set useBrace(journal) 1
set useBrace(key) 1
set useBrace(language) 1
set useBrace(month) 1
set useBrace(note) 1
set useBrace(number) 0
set useBrace(organization) 1
set useBrace(pages) 0
set useBrace(publisher) 1
set useBrace(school) 1
set useBrace(series) 1
set useBrace(title) 1
set useBrace(type) 1
set useBrace(volume) 0
set useBrace(year) 0
set fieldNames [lsort [array names useBrace]]
###########################################################################
# Default values for newly created fields
#
set defFldVal(language) "german"
set fieldDefs [lsort [array names defFldVal]]
###########################################################################
# Search patterns for entries and cite-keys
#
# set bibTopPat {^[ ]*@[a-zA-Z]+[\{\(]([-A-Za-z0-9_:/\.]+)}
# match entry type
set bibTopPat {^[ ]*@([a-zA-Z]+)[\{\(]}
# match cite-key
set bibTopPat1 {^[ ]*@[a-zA-Z]+[\{\(][ ]*([^=, ]+)}
# match type and cite-key
set bibTopPat2 {^[ ]*@([a-zA-Z]+)[\{\(][ ]*([^=, ]+)}
# match first field (no cite-key)
set bibTopPat3 {^[ ]*@([a-zA-Z]+)[\{\(]([ ]*[a-zA-Z]+[ ]*=[ ]*)}
###########################################################################
# BibTeX-mode mode definition
###########################################################################
newModeVar Bib suffixString { \\\\} 0
newModeVar Bib prefixString {% } 0
newModeVar Bib fillColumn {65} 0
newModeVar Bib wordWrap {0} 1
newModeVar Bib autoMark {1} 1
newModeVar Bib wordBreak {[a-zA-Z0-9]+} 0
newModeVar Bib wordBreakPreface {[^a-zA-Z0-9]} 0
newModeVar Bib funcExpr $bibTopPat 0
newModeVar Bib overwriteBuffer {1} 1
newModeVar Bib fieldBraces {1} 1
newModeVar Bib entryBraces {1} 1
newModeVar Bib segregateStrings {1} 1
newModeVar Bib markStrings {0} 1
newModeVar Bib alignEquals {0} 1
###
# newModeVar Bib emacsBibMode {0} 1
# newModeVar Bib addCiteKeys {0} 1
# newModeVar Bib checkSyntax {0} 1
newModeVar Bib zapEmptyFields {0} 1
newModeVar Bib descendingYears {1} 1
###
newModeVar Bib indentString { } 0
newModeVar Bib stdAbbrevs {jan feb mar apr may jun jul aug sep oct nov dec} 0
# newModeVar Bib convert8bitAscii2TeX {0} 1
set bibtexKeyWords {address annote author booktitle
chapter city crossref edition editor howpublished institution
journal key language month note number organization
publisher pages school series title type
volume year}
regModeKeywords -e {%} -m {@} -c red -k blue Bib $bibtexKeyWords
unset bibtexKeyWords
# # Use a shadow proc to keep settings for 8-bit character conversion
# # consistent between TeX and Bib modes.
# #
# trace variable BibmodeVars(convert8bitAscii2TeX) w shadowBib8bitConvert
# proc shadowBib8bitConvert {name1 name2 op} {
# global BibmodeVars TeXmodeVars
#
# # Use TeX-mode routines to actually do the key bindings.
# #
# if {$BibmodeVars(convert8bitAscii2TeX)} then {
# toggle8bitAscii "ascii" "Bib"
# } else {
# toggle8bitAscii "unascii" "Bib"
# }
#
# # Only set TeX flag if necessary, to avoid unnecessary rebinding of keys
# # (It takes enough time to be annoying)
# #
# if {$BibmodeVars(convert8bitAscii2TeX) != $TeXmodeVars(convert8bitAscii2TeX)} then {
# set TeXmodeVars(convert8bitAscii2TeX) $BibmodeVars(convert8bitAscii2TeX)
# }
# }
#
# set BibmodeVars(convert8bitAscii2TeX) $TeXmodeVars(convert8bitAscii2TeX)
###########################################################################
# BibTeX Menu Definition.
###########################################################################
proc bibtexMenu {} {}
set bibtexMenu "•136"
proc bibtex {} {
global bibtexSig
set name [launchBackApplSigs {BIBt Vbib} bibtexSig]
switchTo [file tail $name]
}
proc makeindex {} {
launchForeAppl Midx
}
menu -n $bibtexMenu {
"bibtex"
"(-)"
{menu -n Entries -p makeEntry {}
}
{menu -n Fields -p makeField {}
}
"(-)"
"selectEntry/B<U<B"
"nextEntry/N<U<B"
"prevEntry/P<U<B"
"formatEntry/L<U<B"
"copyCiteKey/C<U<B"
"(-)"
"searchEntries/M<U<B"
"searchFields/F<U<B"
{menu -n sortBy... -p bibSortProc {
"citeKey"
"firstAuthor,Year"
"lastAuthor,Year"
"year,FirstAuthor"
"year,LastAuthor"
}
}
{menu -n sortMarks... -p markSortProc {
"alphabetically"
"byPosition"
}
}
"(-)"
"countEntries"
"formatAllEntries"
}
menu -n Entries -p makeEntry [concat $entryNames {
"(-)"
"customEntry"
} ]
menu -n Fields -p makeField [concat $fieldNames {
"(-)"
"customField"
"multipleFields"
} ]
###########################################################################
# Menu command procs
###########################################################################
proc makeField {menu item} {
global fieldNames
bibFormatSetup
if {$item == "multipleFields"} then {
set flds [listpick -l -L {author year} -p "Pick desired fields:" $fieldNames]
if {[llength flds]} {
set lines {}
foreach fld $flds {
append lines [newField $fld]
}
} else {
return
}
} else {
set lines [newField $item]
}
set pos0 [nextLineStart [getPos]]
goto $pos0
insertText $lines
goto $pos0
nextTabStop
}
proc makeEntry {menu item} {
bibFormatSetup
newEntry $item
}
###########################################################################
# Return the bounds of the bibliographic entry surrounding the current
# position.
#
proc getEntry {pos} {
set pos1 [search -f 0 -r 1 -n -s {[ ]*@[a-zA-Z]*[\{\(]} $pos ]
if {$pos1 == ""} then {
set begPos [nextLineStart $pos]
set endPos $begPos
} else {
set begPos [lineStart [lindex $pos1 0]]
set pos0 [lindex $pos1 1]
set openBrace [getText [expr $pos0-1] $pos0 ]
if {[catch {matchIt $openBrace $pos0} pos1]} {
alertnote "There seems to be a badly delimited field in here. Are entry and field delimiters set correctly?"
goto $begPos
error "Can't find close brace"
} else {
set endPos [nextLineStart $pos1]
}
}
return [list $begPos $endPos]
}
###########################################################################
# Advance to the next bibliographic entry.
#
proc nextEntry {} {
global bibTopPat bibTopPat1 bibTopPat2
# set topPat {[ ]*@([a-zA-Z]+)[\{\(]}
set pos0 [lindex [getEntry [getPos]] 1]
set nextPos [nextLineStart $pos0]
while {![catch {search -f 1 -r 1 -s $bibTopPat $pos0} pos]} {
regexp $bibTopPat [eval getText $pos] mtch type
if {$type != "string"} {
set nextPos [lindex $pos 0]
break
} else {
set pos0 [nextLineStart [lindex $pos 1]]
}
}
goto $nextPos
}
###########################################################################
# Go back to the previous bibliographic entry.
#
proc prevEntry {} {
global bibTopPat bibTopPat1 bibTopPat2
# set topPat {[ ]*@([a-zA-Z]+)[\{\(]}
set pos0 [lindex [getEntry [getPos]] 0]
if {$pos0 > 0} {
set nextPos $pos0
incr pos0 -1
while {![catch {search -f 0 -r 1 -s $bibTopPat $pos0} pos]} {
regexp $bibTopPat [eval getText $pos] mtch type
if {$type != "string"} {
set nextPos [lindex $pos 0]
break
} else {
set pos0 [lineStart [lindex $pos 0]]
if {$pos0 == 0} {break}
incr pos0 -1
}
}
goto $nextPos
}
}
###########################################################################
# Select (highlight) the current bibliographic entry.
#
proc selectEntry {} {
set pos [getEntry [getPos]]
select [lindex $pos 0] [lindex $pos 1]
}
###########################################################################
# Put the cite-key of the current entry on the clipboard.
#
proc copyCiteKey {} {
global bibTopPat2
set limits [getEntry [getPos]]
set top [lindex $limits 0]
set bottom [lindex $limits 1]
if {[regexp -indices $bibTopPat2 [getText $top $bottom] allofit type citekey]} {
select [expr $top+[lindex $citekey 0]] [expr $top+[lindex $citekey 1]+1]
copy
message "Copied \"[getSelect]\""
}
}
###########################################################################
# Create a new bibliographic entry with its required fields.
#
proc newEntry {entryName} {
global entryNames customEntries fieldNames rqdFld optFld myFld defFldVal
global bibOpenEntry bibCloseEntry BibmodeVars
goto [lindex [getEntry [getPos]] 1]
if {$entryName == "customEntry"} {
set lines "@•$bibOpenEntry•,\r"
set theFields [listpick -l -L {author} -p "Pick desired fields:" $fieldNames]
} else {
set lines "@${entryName}$bibOpenEntry•,\r"
if {[lsearch -exact $customEntries $entryName] >= 0 && [llength $myFld($entryName)]} {
set theFields $myFld($entryName)
} elseif {[lsearch -exact $entryNames $entryName] >= 0} {
set theFields $rqdFld($entryName)
} else {
set theFields {}
}
}
set nmlen 0
foreach field $theFields {
set len [string length $field]
if {$len > $nmlen} {set nmlen $len}
}
set theTop [lineStart [getPos]]
foreach field $theFields {
catch {append lines [newField $field $nmlen]}
}
append lines "$bibCloseEntry\r"
insertText $lines
goto $theTop
nextTabStop
}
###########################################################################
# Create a new field within the current bibliographic entry
#
proc newField {fieldName {nmlen 0}} {
global fieldNames useBrace bibOpenQuote bibCloseQuote bibIndent
global fieldDefs defFldVal
set spc " "
if {[lsearch -exact $fieldNames $fieldName] >= 0} {
set needBraces $useBrace($fieldName)
} else {
set needBraces 1
}
if {[lsearch -exact $fieldDefs $fieldName] >= 0} {
set val $defFldVal($fieldName)
} else {
set val "•"
}
if {$nmlen} {
set pad [string range $spc 1 [expr $nmlen - [string length $fieldName]]]
} else {
set pad ""
}
if {$needBraces || $fieldName == "customField"} {
set result "$bibIndent$fieldName =$pad ${bibOpenQuote}${val}${bibCloseQuote},\r"
} else {
set result "$bibIndent$fieldName =$pad $val,\r"
}
return $result
}
proc bibFormatSetup {} {
global bibOpenQuote bibCloseQuote bibIndent BibmodeVars
global bibOpenEntry bibCloseEntry bibAbbrevs
bibFieldDelims
bibEntryDelims
set bibIndent $BibmodeVars(indentString)
regsub {\\t} $bibIndent { } bibIndent
set bibAbbrevs [listStrings]
foreach abbrev $BibmodeVars(stdAbbrevs) {
lappend bibAbbrevs [string tolower $abbrev]
}
}
###########################################################################
# Find all entries that match a given regular expression and copy them to
# a new buffer.
#
proc searchEntries {} {
if [catch {prompt "Regular expression:" ""} reg] return
if {![string length $reg]} return
set reg ^.*$reg.*$
set matches [findEntries $reg]
if {[llength $matches] >0} {
writeEntries $matches 0
} else {
message "No matching entries were found"
}
}
###########################################################################
# Find all entries in which the indicated field matches a given regular
# expression and copy them to a new buffer.
#
proc searchFields {} {
global fieldNames
if {[catch {eval prompt {{Field name:}} "author" {Fields} $fieldNames} fld]} return
if {![string length $fld]} return
if {[catch {prompt "Regular expression:" ""} reg]} return
if {![string length $reg]} return
set matches [findEntries $reg]
if {[llength $matches] == 0} {
return "No matching entries were found"
}
set vals {}
foreach hit $matches {
set pos [lindex $hit 1]
set top [lindex $hit 2]
set bottom [lindex $hit 3]
while {[set failure [expr {[getFldName $pos $top] != $fld}]] &&
![catch {search -f 1 -r 1 -i 1 -m 0 -l $bottom -s -- $reg $pos} mtch]} {
set pos [lindex $mtch 1]
}
if {!$failure} { lappend vals [list $top $bottom] }
}
if {[llength $vals] >0} {
writeEntries $vals 0
} else {
message "No matching entries were found"
}
}
###########################################################################
# Sort all of the entries based on one of various criteria.
#
proc bibSortProc {menu item} {
if {$item == "citeKey"} {
sortByCiteKey
} elseif {$item == "firstAuthor,Year"} {
sortByAuthors 0 0
} elseif {$item == "lastAuthor,Year"} {
sortByAuthors 1 0
} elseif {$item == "year,FirstAuthor"} {
sortByAuthors 0 1
} elseif {$item == "year,LastAuthor"} {
sortByAuthors 1 1
}
}
###########################################################################
# Sort the file marks. (These operations are also available under the
# "Search:NamedMarks" menu)
#
proc markSortProc {menu item} {
if {$item == "alphabetically"} {
sortMarksFile
} elseif {$item == "byPosition"} {
orderMarks
}
}
###########################################################################
# Sort all of the entries in the file alphabetically by author.
#
proc sortByAuthors {{lastAuthorFirst 0} {yearFirst 0}} {
global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
set bibSegStr $BibmodeVars(segregateStrings)
set matches [findEntries $bibTopPat]
set crossrefs [listCrossrefs]
set strings [listStrings]
set vals {}
set others {}
set refs {}
set strs {}
set beg [maxPos]
set end 0
foreach hit $matches {
set pos [lindex $hit 1]
set top [lindex $hit 2]
set bottom [lindex $hit 3]
set entry [getText $top $bottom]
regsub -all "\[\n\r\]+" $entry { } entry
regsub -all "\[ \]\[ \]+" $entry { } entry
regsub {[, ]*[\)\}][ ]*$} $entry { } entry
if {[regexp $bibTopPat1 $entry allofit citeKey]} {
set citeKey [string tolower $citeKey]
set keyExists 1
} else {
set citekey ""
set keyExists 0
}
if {$keyExists && [lsearch -exact $crossrefs $citeKey] >= 0} {
lappend refs [list $pos $top $bottom]
} elseif {$bibSegStr && $keyExists && [lsearch -exact $strings $citeKey] >= 0} {
lappend strs [list $citeKey $top $bottom]
} else {
if {![catch {getFldValue $entry author} fldval]} {
if {[catch {getFldValue $entry year} year]} { set year 9999 }
lappend vals [list [authSortKey $fldval $lastAuthorFirst $year $yearFirst] $top $bottom]
} else {
lappend others [list $pos $top $bottom]
}
}
if {$top < $beg} {set beg $top}
if {$bottom > $end} {set end $bottom}
}
if {$bibSegStr} {
set result [concat $strs $others [lsort $vals] $refs]
} else {
set result [concat $others [lsort $vals] $refs]
}
if {[llength $result] >0} {
writeEntries $result 1 $beg $end
} else {
message "No results of author sort !!??"
}
}
###########################################################################
# Return a list of the cite-keys of all cross-referenced entries.
#
proc listStrings {} {
global bibTopPat bibTopPat1 bibTopPat2
set matches [findEntries {^[ ]*@string *[\{\(]} 0]
message "scanning for @strings…"
foreach hit $matches {
set top [lindex $hit 2]
set bottom [lindex $hit 3]
set entry [getText $top $bottom]
regsub -all "\[\n\r\]+" $entry { } entry
regsub -all "\[ \]\[ \]+" $entry { } entry
regsub {[, ]*[\)\}][ ]*$} $entry { } entry
regexp $bibTopPat1 $entry allofit citekey
set citekey [string tolower $citekey]
if {[catch {incr strings($citekey)} num]} {
set strings($citekey) 1
}
}
if {[catch {lsort [array names strings]} res]} {
set res {}
}
message ""
return $res
}
###########################################################################
# Return a list of the cite-keys of all cross-referenced entries.
#
proc listCrossrefs {} {
set matches [findEntries {crossref}]
catch {unset crossrefs}
message "scanning for crossrefs…"
foreach hit $matches {
set top [lindex $hit 2]
set bottom [lindex $hit 3]
set entry [getText $top $bottom]
regsub -all "\[\n\r\]+" $entry { } entry
regsub -all "\[ \]\[ \]+" $entry { } entry
regsub {[, ]*[\)\}][ ]*$} $entry { } entry
if {![catch {getFldValue $entry crossref} fldval]} {
set fldval [string tolower $fldval]
if {[catch {incr crossref($fldval)} num]} {
set crossrefs($fldval) 1
}
}
}
if {[catch {lsort [array names crossrefs]} res]} {
set res {}
}
message ""
return $res
}
###########################################################################
# Create a sort key from an author list. When sorting entries by author,
# performing the sort using keys should be faster than reparsing the author
# lists for every comparison (the old method :-( ).
#
proc authSortKey {authList lastAuthorFirst {year {}} {yearFirst 0}} {
global BibmodeVars
set pat1 {\\.\{([A-Za-z])\}}
set pat2 {\{([^\{\}]+) ([^\{\}]+)\}}
# Remove enclosing braces, quotes, or whitespace
set auths %[string trim $authList {{}" }]&
# Remove TeX codes for accented characters
regsub -all $pat1 $auths {\1} auths
# Concatenate strings enclosed in braces
while {[regsub -all $pat2 $auths {{\1\2}} auths]} {}
# Remove braces (curly and square)
regsub -all {[][\{\}]} $auths {} auths
# regsub -all {,} $auths { ,} auths
# Replace 'and's with begin-name/end-name delimiters
regsub -all {[ ]and[ ]} $auths { \&% } auths
# Put last name first in name fields without commas
regsub -all {%([^\&,]+) ([^\&, ]+) *\&} $auths {%\2,\1\&} auths
# Remove begin-name delimiters
regsub -all {%} $auths {} auths
# Remove whitespace surrounding name separators
regsub -all {[ ]*\&[ ]*} $auths {\&} auths
# Replace whitespace separating words with shrieks
regsub -all {[ ,]+} $auths {!} auths
# If desired, move last author to head of sort key
if {$lastAuthorFirst} {
regsub {(.*)&([^&]+)&?$} $auths {\2\&\1} auths
}
# If provided, sort by year (descending order) as well
regsub {^[^0-9]*([0-9]*).*$} $year {\1} year
if {$year != {}} {
if {$BibmodeVars(descendingYears)} { catch {set year [expr 9999-$year]} }
if {$yearFirst} {
set auths "$year&$auths"
} else {
regsub {^([^&]+)(&?)} $auths "\\1\\&${year}\\2" auths
}
}
return $auths
}
###########################################################################
# Sort all of the entries in the file alphabetically by their cite-keys.
#
proc sortByCiteKey {} {
global bibTopPat bibTopPat1 bibTopPat2 BibmodeVars
set bibSegStr $BibmodeVars(segregateStrings)
set matches [findEntries $bibTopPat]
set crossrefs [listCrossrefs]
set strings [listStrings]
set begEntries [maxPos]
set endEntries 0
set strs {}
set vals {}
set refs {}
foreach hit $matches {
set beg [lindex $hit 0]
set end [lindex $hit 1]
set top [lindex $hit 2]
set bottom [lindex $hit 3]
if {[regexp $bibTopPat1 [getText $top $bottom] allofit citekey]} {
set citekey [string tolower $citekey]
set keyExists 1
} else {
set citekey "000000$beg"
set keyExists 0
}
if {$keyExists && [lsearch -exact $crossrefs $citekey] >= 0} {
lappend refs [list $top $top $bottom]
} elseif {$keyExists && $bibSegStr && [lsearch -exact $strings $citekey] >= 0} {
lappend strs [list $citekey $top $bottom]
} else {
lappend vals [list $citekey $top $bottom]
}
if {$top < $begEntries} {set begEntries $top}
if {$bottom > $endEntries} {set endEntries $bottom}
}
if {$bibSegStr} {
set result [concat $strs [lsort $vals] $refs]
} else {
set result [concat [lsort $vals] $refs]
}
if {[llength $result] >0} {
writeEntries $result 1 $begEntries $endEntries
} else {
message "No results of cite-key sort !!??"
}
}
###########################################################################
# Search for all entries matching a given regular expression. The results
# are returned in a list, each element of which is a list of four integers:
# the beginning and end of the matching entry and the beginning and end of
# the matching string. Adapted from "matchingLines" in "misc.tcl".
#
proc findEntries {reg {casesen 1}} {
if {![string length $reg]} return
set pos 0
set result {}
while {![catch {search -f 1 -r 1 -m 0 -i $casesen -s $reg $pos} mtch]} {
set entry [getEntry [lindex $mtch 0]]
lappend result [concat $mtch $entry]
set pos [lindex $entry 1]
}
return $result
}
###########################################################################
# Return a list containing the data for the current entry, indexed by
# the parameter names, e.g., "author", "year", etc. Index names for the
# entry type and cite-key are "type" and "citekey".
#
proc getFields {pos} {
global bibTopPat bibTopPat1 bibTopPat2 bibTopPat3
set fldPat {[ ]*([a-zA-Z]+)[ ]*=[ ]*}
set limits [getEntry $pos]
set top [lindex $limits 0]
set bottom [lindex $limits 1]
set entry [getText $top $bottom]
regsub -all "\[\n\r\]+" $entry { } entry
regsub -all "\[ \]\[ \]+" $entry { } entry
#
regsub {[, ]*[\)\}][ ]*$} $entry { } entry
if {[regexp -indices $bibTopPat2 $entry mtch theType theKey ]} {
set key [string range $entry [lindex $theKey 0] [lindex $theKey 1]]
set theRest [expr 1 + [lindex $mtch 1]]
} elseif {[regexp -indices $bibTopPat3 $entry mtch theType aField]} {
set key {}
set theRest [lindex $aField 0]
} else {
error "Invalid entry"
}
lappend names type
set type [string tolower [string range $entry [lindex $theType 0] [lindex $theType 1]]]
lappend data [list $type]
lappend names citekey
lappend data $key
set entry ",[string range $entry $theRest end]"
set fldPat {,[ ]*([^ =,]+)[ ]*=[ ]*}
set name {}
while {[regexp -indices $fldPat $entry mtch sub1]} {
set nextName [string range $entry [lindex $sub1 0] [lindex $sub1 1]]
lappend names [string tolower $nextName]
if {$name != ""} {
set prevData [string range $entry 0 [expr [lindex $mtch 0]-1]]
lappend data [breakIntoLines [bibFieldData $prevData]]
}
set name $nextName
set entry [string range $entry [expr [lindex $mtch 1]+1] end]
}
lappend data [breakIntoLines [bibFieldData $entry]]
return [list $names $data]
}
proc bibFieldData {text} {
set text [string trim $text { ,#}]
set text1 [string trim $text {\{\}\" }]
if {[string match {*[\{\}\"]*} $text1]} {
set words [parseWords $text]
if {[llength $words]==1} {
regsub {^[\{\"\']} $text {} text
regsub {[\}\"\']$} $text {} text
}
} else {
set text $text1
}
return $text
}
###########################################################################
# Extract the data from the indicated field of an entry, which is passed
# as a single string. This version tries to be completely general,
# allowing nested braces within data fields and ignoring escaped
# delimiters. (derived from proc getField).
#
proc getFldValue {entry fldname} {
set fldPat "\[ \]*${fldname}\[ \]*=\[ \]*"
set fldPat2 {,[ ]*([^ =,]+)[ ]*=[ ]*}
set slash "\\"
set qslash "\\\\"
set ok [regexp -indices -nocase $fldPat $entry mtch]
if {$ok} {
set pos [expr [lindex $mtch 1] + 1]
set entry [string range $entry $pos end]
if {[regexp -indices $fldPat2 $entry mtch sub1]} {
set entry [string range $entry 0 [expr [lindex $mtch 0]-1]]
}
set fld [bibFieldData $entry]
return $fld
} else {
error "field not found"
}
}
###########################################################################
# Parse the entry around position "pos" and rewrite it to the original
# buffer in a canonical format
#
proc formatEntry {} {
global useBrace bibOpenQuote bibCloseQuote
global bibOpenEntry bibCloseEntry bibIndent
set spc " "
bibFormatSetup
set pos [getPos]
set limits [getEntry $pos]
set top [lindex $limits 0]
set bottom [lindex $limits 1]
if {![catch {bibFormatEntry $pos} result]} {
set oldEntry [getText $top $bottom]
if {$result != $oldEntry} {
deleteText $top $bottom
insertText $result
}
goto $top
nextEntry
} else {
message "Couldn't format this entry for some reason"
}
}
###########################################################################
# Parse the entry around position "pos" and rewrite it to the original
# buffer in a canonical format
#
proc formatAllEntries {} {
global useBrace bibOpenQuote bibCloseQuote
global bibOpenEntry bibCloseEntry bibIndent
set spc " "
bibFormatSetup
# This little dance handles the case that the first
# entry starts on the first line
#
set hit [getEntry [getPos]]
if {[lindex $hit 0] == [lindex $hit 1]} {
nextEntry
set hit [getEntry [getPos]]
}
while {[getPos] < [lindex $hit 1]} {
set top [lindex $hit 0]
set bottom [lindex $hit 1]
if {![catch {bibFormatEntry $top} result]} {
set oldEntry [getText $top $bottom]
if {$result != $oldEntry} {
deleteText $top $bottom
insertText $result
}
}
goto $top
nextEntry
set hit [getEntry [getPos]]
}
}
###########################################################################
# Parse the entry around position "pos" and rewrite it in a canonical format.
# The formatted entry is returned.
#
proc bibFormatEntry {pos} {
global useBrace bibOpenQuote bibCloseQuote
global bibOpenEntry bibCloseEntry bibIndent
global rqdFld optFld BibmodeVars bibAbbrevs
set spc " "
#
# note: calling proc must call "bibFormatSetup" before calling "bibFormatEntry"
#
set limits [getEntry $pos]
set top [lindex $limits 0]
set bottom [lindex $limits 1]
if {[catch {getFields $pos} flds]} {
error "bibFormatEntry: Getflds couldn't find any"
}
set names [lindex $flds 0]
set vals [lindex $flds 1]
set nfld [llength $names]
set type [string tolower [lindex $vals 0]]
set citekey [lindex $vals 1]
# message "$citekey"
# Don't process @string entries
if {$type == "string"} {
set lines [getText $top $bottom]
return $lines
}
# Find length of longest field name
set nmlen 0
foreach nm $names {
set len [string length $nm]
if {$len > $nmlen} { set nmlen $len }
if {![info exists useBrace($nm)]} { set useBrace($nm) 0 }
}
# Format first line
set lines "@${type}${bibOpenEntry}${citekey},\r"
# Format each field on a separate line
for {set ifld 2} {$ifld < $nfld} {incr ifld} {
set nm [lindex $names $ifld]
set vl [lindex $vals $ifld]
if {$vl != "" || ! $BibmodeVars(zapEmptyFields) ||
[lsearch $rqdFld($type) $nm] >= 0} {
set pad [expr $nmlen - [string length $nm]]
if {$BibmodeVars(alignEquals)} {
set pref "${bibIndent}$nm[string range $spc 1 $pad] ="
} else {
set pref "${bibIndent}$nm =[string range $spc 1 $pad]"
}
set ind [string range $spc 1 [string length $pref]]
# Delimit field, if appropriate
set noBrace [expr ($useBrace($nm) == 0 && [isNum $vl]) || [hasCat $vl]]
if {$noBrace == 0 && [string first " " $vl] < 0} {
set noBrace [expr [lsearch $bibAbbrevs [string tolower $vl]] >= 0]
}
if {$noBrace != 0} {
set vl "$vl,"
} else {
set vl "${bibOpenQuote}${vl}${bibCloseQuote},"
}
set pieces [split $vl "\r"]
append lines "$pref [lindex $pieces 0]\r"
foreach piece [lrange $pieces 1 end] {
append lines "$ind $piece\r"
}
}
}
append lines "$bibCloseEntry\r"
return $lines
}
###########################################################################
# Get the name of the field that starts before the given position,
# $pos. The positions $top and $bottom restrict the range of the
# search for the beginning and end of the field; typically, $top and
# $bottom will be the limits of a given entry.
#
proc getFldName {pos top} {
set fldPat {[, ]+([^ =,\{\}\"\']+)[ ]*=[ ]*}
if {![catch {search -f 0 -r 1 -m 0 -i 1 -s -limit $top "$fldPat" $pos} mtch]} {
set theText [eval getText $mtch]
regexp -nocase $fldPat $theText allofit fldnam
return $fldnam
} else {
return {citekey}
}
}
###########################################################################
# Set the quote characters for quoted fields based on the value of the
# flag $bibUseBrace
#
proc bibFieldDelims {} {
global BibmodeVars bibOpenQuote bibCloseQuote
if {$BibmodeVars(fieldBraces)} then {
set bibOpenQuote "{"
set bibCloseQuote "}"
} else {
set bibOpenQuote {"}
set bibCloseQuote {"}
}
}
proc bibEntryDelims {} {
global BibmodeVars bibOpenEntry bibCloseEntry
if {$BibmodeVars(entryBraces)} then {
set bibOpenEntry "{"
set bibCloseEntry "}"
} else {
set bibOpenEntry "("
set bibCloseEntry ")"
}
}
proc isBibFile {} {
set fileName [car [winNames -f]]
set ext [file extension $fileName]
return [string match ".bib" [string tolower $ext]]
}
proc hasNumVal {str} {
expr ! [catch {expr $str}]
}
proc isNum {str} {
regexp {^[ ]*[0-9]+[ ]*$} $str mtch
}
proc hasCat {str} {
regexp {\#} $str mtch
}
###########################################################################
# Take a list of lists that point to selected entries and copy these into
# a new window. The beginning and ending positions for each entry must
# be the last two items in each sublist. The rest of the sublists are
# ignored. It is assumed that each sublist has the same number of items.
#
proc writeEntries {entryPos nondestructive {beg {0}} {end {-1}}} {
global BibmodeVars
if {$end < 0} {set end [maxPos]}
set llen [expr [llength [lindex $entryPos 0]] - 1]
set llen1 [expr $llen-1]
foreach entry $entryPos {
set limits [lrange $entry $llen1 $llen]
append lines [eval getText $limits]
}
set overwriteOK [expr $nondestructive || ! [isBibFile]]
if {$BibmodeVars(overwriteBuffer) && $overwriteOK} {
deleteText $beg $end
insertText $lines
goto $beg
} else {
set begLines [getText 0 [lineStart $beg]]
set endLines [getText [nextLineStart $end] [maxPos]]
new -n {*BibTeX Sort/Search*}
newMode Bib
insertText $begLines
insertText $lines
insertText $endLines
goto $beg
setWinInfo dirty 0
catch shrinkWindow
}
}
###########################################################################
# Set a named mark for each entry, using the cite-key name
#
proc BibMarkFile {} {
global BibmodeVars
global bibTopPat bibTopPat1 bibTopPat2
set pos 0
while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat1 $pos} res]} {
set start [lindex $res 0]
set end [nextLineStart $start]
set text [getText $start $end]
set lab ""
if {[regexp $bibTopPat2 $text mtch type citekey]} {
if {[string tolower $type] != "string" || $BibmodeVars(markStrings)} {
setNamedMark $citekey [lineStart [expr $start - 1]] $start $start
}
}
set pos $end
}
}
###########################################################################
# Report the number of entries of each type
#
proc countEntries {} {
global entryNames
global bibTopPat bibTopPat1 bibTopPat2
set pos 0
set count 0
catch {unset type}
while {![catch {search -f 1 -r 1 -m 0 -i 0 -s $bibTopPat $pos} res]} {
incr count
set start [lindex $res 0]
set end [nextLineStart $start]
set text [getText $start $end]
set lab ""
if {[regexp $bibTopPat $text mtch entryType]} {
set entryType [string tolower $entryType]
if {[catch {incr type($entryType)} num]} {
set type($entryType) 1
}
}
set pos $end
}
new -n {*BibTeX Statistics*}
newMode Bib
foreach name [lsort [array names type]] {
if {$type($name) > 0} {
append lines [format "%4.0d %s\n" $type($name) $name]
}
}
append lines "---- -----------------\n"
append lines [format "%4.0d %s\n" $count "Total entries"]
insertText $lines
goto 0
setWinInfo dirty 0
catch {shrinkWindow 1}
}
#--------------------------------------------------------------------------
# command-double-clicking:
#--------------------------------------------------------------------------
###########################################################################
# In Bib mode, Cmd-double-clicks resolve abbrevs and cross-refs
#
proc BibDblClick {from to} {
global bibTopPat bibTopPat1 bibTopPat2
set limits [getEntry $from]
set top [lindex $limits 0]
set bottom [lindex $limits 1]
# Extend selection to largest string that could be an entry reference
set text [string trim [eval getText [BibExtendClick $from $to $top $bottom]]]
# Get the citekey of current entry, so we can avoid jumping to it
set citekey {}
regexp $bibTopPat2 [getText $top $bottom] mtch type citekey ]
set fldName [getFldName $from $top]
if {[string length $text] == 0 || $text == $citekey || $fldName == $text ||
($fldName == "citekey" && [string tolower $type] != "string")} {
message "Command-double-click on abbreviations and crossref arguments"
return
}
# Jump to the mark for the specified citation, if a mark exists...
# ...otherwise, do an ordinary search for the cite key
pushMark
set searchPat "$bibTopPat\[ \]*[quoteExpr $text]\[ ,\}\)\]"
if {![catch {search -f 1 -r 1 -i 1 -m 0 $searchPat 0} mtch]} {
goto [lindex $mtch 0]
} else {
popMark
select $from $to
if {$fldName == "crossref"} {
message "Cross-reference \"$text\" not found"
} else {
message "Command-double-click on abbreviations and crossref arguments"
}
return
}
message "Use Ctl-. to return to original position"
return
}
# Extend the selection around the initial selection {$from,$to}
# Extension is restricted to the range {$top,$bottom} (the current entry)
proc BibExtendClick {from to top bottom} {
if {$to == 0} { set to $from }
set result [list $from $to]
if {![catch {search -f 0 -r 1 -s -m 0 -l $top "\[,\{\]\"\'=" $from} mtch0]} {
if {![catch {search -f 1 -r 1 -s -m 0 -l $bottom "\[,\}\]\"\'=" $to} mtch1]} {
set from [lindex $mtch0 1]
set to [lindex $mtch1 0]
# Check for illegal chars embedded in the selection
if {[regexp "\[\{\}\]=" [getText $from $to]] == 0} {
set result [list $from $to]
}
}
}
return $result
}
###########################################################################
proc dummyBibTeX {} {
global BibmodeVars TeXmodeVars
# if {$BibmodeVars(convert8bitAscii2TeX) != $TeXmodeVars(convert8bitAscii2TeX)} {
# set BibmodeVars(convert8bitAscii2TeX) $TeXmodeVars(convert8bitAscii2TeX)
# }
}
#
#===============================================================================
proc pcite {} {
set words [getline "Citation keys" ""]
if {![llength $words]} {error "No keys"}
set pattern {@}
foreach w $words {
append pattern "(\[^@\]+$w)"
}
foreach entry [findEntries $pattern] {
set res [getFields [car $entry]]
set title [lindex [cadr $res] [lsearch [car $res] "title"]]
set citekey [lindex [cadr $res] [lsearch [car $res] "citekey"]]
set matches($title) $citekey
set where($title) [car $entry]
}
if {![info exists matches]} {alertnote "No citations"; return}
set title [listpick -p "Citation?" [lsort [array names matches]]]
putScrap $matches($title)
alertnote $matches($title)
goto $where($title)
}